package com.xm.util;

/**
 * 雪花算法
 * SnowflakeIdUtil类用于生成唯一ID，基于雪花算法实现。
 * 工作机器ID和数据中心ID都是5位数，序列号是12位数。因此，这个雪花算法最多可以生成的位数为：
 * 1位符号位 + 41位时间戳位 + 5位数据中心ID位 + 5位工作机器ID位 + 12位序列号位 = 64位
 * @author 康文程
 * @date 2023-12-11 下午 03:57
 */
public class SnowflakeIdUtil {
    /**
     * 设置起始时间戳，这里以2021-01-01 00:00:00为例
     */
    private static final long EPOCH = 1609459200000L;
    /**
     * 工作机器ID 5位数
     */
    private static final long WORKER_ID_BITS = 5L;
    /**
     * 数据中心ID 5位数
     */
    private static final long DATA_CENTER_ID_BITS = 5L;
    /**
     * 工作机器ID的最大取值。
     */
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
    /**
     * 数据中心ID的最大取值。
     */
    private static final long MAX_DATA_CENTER_ID = ~(-1L << DATA_CENTER_ID_BITS);
    /**
     * 序列号所占的位数。
     */
    private static final long SEQUENCE_BITS = 12L;
    /**
     * 位移量，用于计算生成的唯一ID。
     */

    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
    private static final long DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
    private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS;

    /**
     * 序列号的掩码，用于限制序列号的取值范围。
     */
    private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
    /**
     * 序列号，用于在同一毫秒内生成的多个ID之间进行区分。它的初始值是0，
     * 并且每次生成ID时都会递增。当序列号达到最大值时（根据定义的位数决定），会等待下一个毫秒再生成ID。
     */
    private static long sequence = 0L;
    /**
     * 上一次生成ID的时间戳。它的初始值是-1，表示还没有生成过ID。在生成ID时，会将当前的时间戳赋值给lastTimestamp，
     * 并用它与下一个生成ID的时间戳进行比较，确保时间戳的递增性。
     */
    private static long lastTimestamp = -1L;
    /**
     * 生成唯一ID
     *
     * @param workerId     工作机器ID 同一时间戳内可以由该节点生成的最大ID数
     * @param dataCenterId 数据中心ID 数据中心ID是雪花算法中的另一个概念，用于在多个数据中心部署的情况下，区分不同的数据中心。
     *                     具体来说，数据中心ID也是一个固定长度的二进制数字，通常是一个整数值，用于标识不同的数据中心。
     * @return 唯一ID
     */
    public static synchronized long generateId(long workerId, long dataCenterId) {
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException("工作机器ID 不能大于 " + MAX_WORKER_ID + " 或小于 0");
        }
        if (dataCenterId > MAX_DATA_CENTER_ID || dataCenterId < 0) {
            throw new IllegalArgumentException("数据中心 ID 不能大于 " + MAX_DATA_CENTER_ID + " 或小于 0");
        }

        long timestamp = System.currentTimeMillis();
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("时钟向后移动。拒绝生成 " + (lastTimestamp - timestamp) + " 毫秒的 ID");
        }
        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & SEQUENCE_MASK;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }
        lastTimestamp = timestamp;

        return ((timestamp - EPOCH) << TIMESTAMP_LEFT_SHIFT) |
                (dataCenterId << DATA_CENTER_ID_SHIFT) |
                (workerId << WORKER_ID_SHIFT) |
                sequence;
    }

    /**
     * 确保生成的时间戳递增
     * @param lastTimestamp 上一次生成唯一ID的时间戳
     * @return 返回该时间戳作为下一次生成ID的基准
     */
    private static long tilNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }

    /**
     * 使用方法
     * @param args 数组
     */
    public static void main(String[] args) {
        //389326888323125248
        //389327748377743360
        long id = generateId(1, 1);
        System.out.println("Generated ID: " + id);
    }
}